package me.ezjs.framework.core.dao.hibernate;

import me.ezjs.framework.core.model.*;
import org.hibernate.Query;

import java.util.*;

/**
 * sql语句中各个别名的生成器
 * Created by zero-mac on 16/3/10.
 */
public class AliasGenerator {

    private static final String SPACE = " ";
    private static final String DOT = ".";

    private String entityName;//主表的类名
    private TableNode tableRootNode;//存放表直接的关联关系

    //字句
    private StringBuilder fromClause;
    private StringBuilder whereClause;
    private StringBuilder orderClause;

    public AliasGenerator(String entryName) {
        this.entityName = entryName;
        tableRootNode = new TableNode(entityName);
    }

    public String getEntityAlias() {
        return tableRootNode.getAliasName();
    }

    public void analyse(DAOFilter filter) {
        analyseAndBuildWhereClause(filter);

        buildFromClause();
        buildOrderClause(filter);
    }

    private void buildFromClause() {
        fromClause = new StringBuilder();
        fromClause.append(entityName + SPACE + tableRootNode.getAliasName());// = "entityName obj0";

        TableNode.generateJoinHqlOfFromClause(tableRootNode, fromClause);
    }

    private void handleParameter(Parameter parameter) {
        String field = parameter.getKey();
        if (field.contains(".")) {
            StringTokenizer st = new StringTokenizer(field, ".");
            int allCount = st.countTokens();
            int index = 0;

            TableNode father = tableRootNode;
            while (st.hasMoreElements()) {
                index++;
                if (index == allCount) {
                    break;
                }
                String tableName = (String) st.nextElement();
                if (tableName.equals(entityName)) {
                    continue;
                }

                // TODO: 16/3/11 JoinType的类型应该是可自定义的 
                TableNode table = father.addChild(tableName, JoinType.INNER);
                parameter.setAliasKey(table.getAliasName() + field.substring(field.lastIndexOf('.')));

                father = table;
            }
        } else {
            parameter.setAliasKey(field);
        }
    }

    /**
     * 分析select条件.
     *
     * @param filter
     */
    private void analyseSelect(DAOFilter filter) {
        filter.getSelect();//select字句
    }

    /**
     * 分析where条件.
     *
     * @param filter
     */
    private void analyseAndBuildWhereClause(DAOFilter filter) {
        ParamTree paramTree = filter.getParamTree();
        whereClause = new StringBuilder();
        analyseNode(paramTree.getRootNode(), "B0", true);

    }

    private void analyseNode(ParamNode node, String pre, boolean isRoot) {
        if (node.getRelation() != null) {
            ParamNode.Relation relation = node.getRelation();
            //relation决定了其子节点的并列关系
            if (node.getNodes() != null) {
                if (!isRoot) whereClause.append("(");
                int index = 0;
                for (ParamNode pn :
                        node.getNodes()) {
                    if (index > 0) {
                        whereClause.append(relation.getSql());
                    }
                    String next = pre + index++;
                    //继续分析
                    analyseNode(pn, next, false);
                }
                if (!isRoot) whereClause.append(")");

            }
        } else if (node.getParameter() != null) {
            Parameter param = node.getParameter();
            //set bind name
            param.setBindName(pre);
            //update alias by fieldName
            handleParameter(param);
            whereClause.append(OperateUtil.buildOperateClause(param));
        }
    }

    /**
     * 分析sort条件.仅仅支持当前表字段的排序.
     *
     * @param filter
     */
    private void buildOrderClause(DAOFilter filter) {
        List<Sort> sl = filter.getSl();//order字句
        orderClause = new StringBuilder();

        int index = 0;
        for (Sort sort :
                sl) {
            index++;
            String field = sort.getField();
            if (index > 1) {
                orderClause.append(", ");
            }
            orderClause.append(field + " " + (sort.isDesc() ? "DESC" : "ASC"));
        }
    }

    public String getFromClause() {
        return " from " + fromClause.toString();
    }

    public String getWhereClause() {
        return " where " + whereClause.toString();
    }

    public String getOrderClause() {
        if (orderClause == null || orderClause.toString().length() == 0) {
            return "";
        }
        return " order by " + orderClause.toString();
    }

    public void bindParameters4Filter(Query query, DAOFilter filter) {
        ParamTree paramTree = filter.getParamTree();

        bindNodeParameter(paramTree.getRootNode(), query);
    }

    private void bindNodeParameter(ParamNode node, Query query) {
        if (node.getRelation() != null) {
            if (node.getNodes() != null) {
                for (ParamNode pn :
                        node.getNodes()) {
                    //递归
                    bindNodeParameter(pn, query);
                }
            }
        } else if (node.getParameter() != null) {
            Parameter param = node.getParameter();
            // bind parameter
            bindPara(param, query);
        }
    }


    protected final static void bindPara(Parameter p, Query query) {
        Object value = p.getValue();
        String bind = p.getBindName();
        switch (p.getOperate()) {
            case ISNULL:
            case NOTNULL:
                break;
            case EQUAL:
            case NOTEQUAL:
            case GREAT:
            case GREATEQUAL:
                query.setParameter(bind, value);
                break;
            case LESS:
            case LESSEQUAL:
                if (isStringAndEmpty(value)) {
                    value = p.getEnd();
                }
                query.setParameter(bind, value);
                break;
            case LIKE:
                if (value instanceof Date) {
                    query.setParameter(bind, "%" + value + "%");
                } else {
                    query.setParameter(bind, "%" + sqlEncode((String) value, true) + "%");
                }
                break;
            case LOWERLIKE:
                if (value instanceof Date) {
                    query.setParameter(bind, "%" + value + "%");
                } else {
                    query.setParameter(bind, "%" + sqlEncode(((String) value).trim().toLowerCase(), true) + "%");
                }
                break;
            case BETWEEN:
                query.setParameter(bind + "lo", p.getBegin());
                query.setParameter(bind + "hi", p.getEnd());
                break;
            case IN:
            case NOTIN:
                if (value.getClass().isArray()) {
                    query.setParameterList(bind, (Object[]) value);
                } else {
                    query.setParameterList(bind, (Collection<?>) value);
                }
                break;
            default: // impossible
                break;
        }
    }


    /**
     * 转换特殊字符
     *
     * @param filedValue 字段值
     * @return
     */
    protected final static String sqlEncode(String filedValue, boolean likeQuery) {
        String tempValue = filedValue;
        tempValue = tempValue.replace("'", "''");

        if (likeQuery) {
            // tempValue = tempValue.replace("[","[[]%");
            tempValue = tempValue.replace("\\", "\\\\");
            tempValue = tempValue.replace("_", "\\_");
            tempValue = tempValue.replace("＿", "'\\_'");
            tempValue = tempValue.replace("%", "\\%");
        }

        return tempValue;
    }

    protected static boolean isStringAndEmpty(Object value) {
        if (value == null) {
            return true;
        }
        if (!(value instanceof String)) {
            return false;
        }
        if (((String) value).trim().length() == 0) {
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Parameter p1 = new Parameter("sMonth", 3, Operate.LESSEQUAL);
        Parameter p2 = new Parameter("bMonth", 4, Operate.GREATEQUAL);

        Parameter p3 = new Parameter("boy.sMonth", 5, Operate.LESSEQUAL);
        Parameter p4 = new Parameter("test.bMonth", 6, Operate.GREATEQUAL);
        Parameter p5 = new Parameter("baby.angel.bMonth", 7, Operate.ISNULL);

        Parameter p6 = new Parameter("girl.bMonth", 8, Operate.GREATEQUAL);

        // 组成 (p1 & p2) or (p3 & p4)
        DAOFilter filter = new DAOFilter(true);
        filter.addSearch(new ParamNode(ParamNode.Relation.and, p1, p2));
        filter.addSearch(p6);
        filter.addSearch(new ParamNode(ParamNode.Relation.and, p3, p4, p5));

        AliasGenerator test = new AliasGenerator("test");
        test.analyse(filter);
        System.out.println(test.getWhereClause());
        System.out.println(test.getFromClause());
    }


}
